package com.pic.mgt.manager.terminal;

import java.io.File;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.dao.DataAccessException;

import com.pic.mgt.dao.ITerminalMgtDao;
import com.pic.mgt.log.IgxLog;
import com.pic.mgt.utils.IgxCollectionUtils;


public abstract class SimpleTerminalManager implements TerminalManager {
	protected String haishinOutboxPathStr;
	protected int terminalIdLen;
	
	protected TerminalManagementListener terminalManagementListener;

	// Dao
	protected ITerminalMgtDao terminalMgtDao;
	
	// 端末清單
	protected Map<String, BasicTerminalInfoBean> basicTerminalInfoBeanMap;
	
	protected SimpleTerminalManager(ITerminalMgtDao terminalMgtDao,
									String haishinOutboxPathStr,
									int terminalIdLen)
										throws TerminalManagementOperationException {
		super();
		
		this.terminalMgtDao = terminalMgtDao;
		this.haishinOutboxPathStr = haishinOutboxPathStr;
		this.terminalIdLen = terminalIdLen;
		this.basicTerminalInfoBeanMap = new HashMap<String, BasicTerminalInfoBean>();
		
		// 取得第一份有效清單出來
		// throws TerminalManagementOperationException
		getFirstBasicTerminalInfoBeanList();
	}
	
	private void getFirstBasicTerminalInfoBeanList()
				throws TerminalManagementOperationException {
		// 第一份有效清單為,存在於資料庫並且對應的目錄也存在的"有效"端末

		// 取得資料庫中所有的端末
		List<BasicTerminalInfoBean> basicTerminalInfoBeanInDbList;
		try {
			basicTerminalInfoBeanInDbList =
				this.terminalMgtDao.findAllTerminalsBasicInfo();
		} catch (DataAccessException e) {
			try {
				throw new TerminalManagementOperationException(
						"SUDA-257 Building effective terminal list failed. Due to database operation error.", e);
			} catch (TerminalManagementOperationException e1) {
				// ********************** Log **********************
				IgxLog.messageLog.error(this, e1.getMessage());
				// ********************** Log **********************

				throw e1;
			}
		}

		// 從 Haishin Outbox 目錄底下取得所有端末ID目錄名稱 List
		List<String> terminalFolderIdList = getTerminalFolderIdListUnderHaishinOutboxPath();
		// 將List轉換為Map(Value為null)
		Map<String, Object> terminalFolderIdMap = IgxCollectionUtils.covertStringListToMap(terminalFolderIdList);

		// 開始產第一份有效清單(如果是有效端末,並且有對應的目錄存在,則加入清單)
		for (BasicTerminalInfoBean basicTerminalInfoBeanInDb : basicTerminalInfoBeanInDbList) {
			if (isEffectiveTerminalId(basicTerminalInfoBeanInDb)) { // 表示為有效端末
				String terminalIdStrInDb = basicTerminalInfoBeanInDb.getTerminalId();
				if (terminalIdStrInDb.length() != this.terminalIdLen) {
					// 若端末ID長度不正確則忽略此端末
					// ********************** Log **********************
					IgxLog.messageLog.error(this,
							"SUDA-258 Building terminal list: the length of terminal id '{}' in database is incorrect.",
							terminalIdStrInDb);
					// ********************** Log **********************
					continue;
				}
				if (terminalFolderIdMap.containsKey(terminalIdStrInDb)) { // 表示有對應的目錄存在
					// 加入端末清單
					this.basicTerminalInfoBeanMap.put(terminalIdStrInDb, basicTerminalInfoBeanInDb);
				}
			}
		}
	}
	
	@Override
	/*
	 * 此方法負責做將位於資料庫中的端末清單與有效清單做比對，真正新增或刪除的動作細節
	 * 交由子類別執行。
	 */
	public synchronized void synchronizeTerminals() throws TerminalManagementOperationException {
		/*
		 * (1) 從資料庫中取得所有端末基本資訊的 List
		 * (2) 檢查資料庫中端末ID的長度(忽略長度不正確的)
		 * (3) 將端末基本資訊的 List轉換成 Map
		 * (4) 從 Haishin Outbox 目錄底下取得所有端末ID目錄名稱 List
		 * (5) 將端末ID目錄名稱 List轉換成Map (Value為null)
		 * (6) 比對需要新增的端末並產生之
		 * (7) 比對需要刪除的端末並刪除之
		 * (8) 檢查目前的端末清單中是否有結束日期已到需要失效的端末
		 */
		
		// ********************** Log **********************
		IgxLog.messageLog.info(this,
				"Synchronizing terminals from database.");
		// ********************** Log **********************
		
		synchronized (this.basicTerminalInfoBeanMap) {
			List<BasicTerminalInfoBean> basicTerminalInfoBeanInDbList;
			
			// (1) 取得資料庫中所有的端末的資本資訊 List
			try {
				// throws DataAccessException
				basicTerminalInfoBeanInDbList = getTerminalIdListFromDB();
			} catch (DataAccessException e) {
				// ********************** Log **********************
				IgxLog.messageDetailsLog.error(this, e.getMessage());
				// ********************** Log **********************
				
				try {
					throw new TerminalManagementOperationException(
							"SUDA-259 Synchronizing terminals from database failed in database operation.",
							e);
				} catch (TerminalManagementOperationException e1) {
					// ********************** Log **********************
					IgxLog.messageLog.error(this, e1.getMessage());
					// ********************** Log **********************
					
					throw e1;
				}
			}
			
			// (2) 檢長資料庫端末ID的長度(忽略長度不正確的)
			basicTerminalInfoBeanInDbList = filterInCorrectTerminalIdLen(
												basicTerminalInfoBeanInDbList);
			
			
			// (3) 將於DB內的端末基本資訊轉換成Map (Key:端末ID;Value:BasicTerminalInfoBean)
			Map<String, BasicTerminalInfoBean> basicTerminalInfoBeanInDbMap =
					covertBasicTerminalInfoBeanListToMap(basicTerminalInfoBeanInDbList);
			
			// (4) 從 Haishin Outbox 目錄底下取得所有端末ID目錄名稱 List
			List<String> terminalFolderIdList = getTerminalFolderIdListUnderHaishinOutboxPath();
			
			// (5) 將端末ID目錄名稱 List轉換成Map (Value為null)
			Map<String, Object>  terminalFolderIdMap =
							IgxCollectionUtils.covertStringListToMap(terminalFolderIdList);
			
			// (6) 比對需要新增的端末並產生之
			try {
				// throws TerminalControlOperationException
				synchronizeAddTerminals(terminalFolderIdMap, basicTerminalInfoBeanInDbMap);
			} catch (TerminalManagementOperationException e) {
				try {
					throw new TerminalManagementOperationException(
							"SUDA-260 Synchronizing terminals from database failed in terminal folder operation.",
							e);
				} catch (TerminalManagementOperationException e1) {
					// ********************** Log **********************
					IgxLog.messageLog.error(this, e1.getMessage());
					// ********************** Log **********************
					
					throw e1;
				}
			}
			
			// (7) 比對需要刪除的端末並刪除之
			try {
				// throws TerminalControlOperationException
				synchronizeRemoveTerminals(terminalFolderIdMap, basicTerminalInfoBeanInDbMap);
			} catch (TerminalManagementOperationException e) {
				try {
					throw new TerminalManagementOperationException(
							"SUDA-260 Synchronizing terminals from database failed in terminal folder operation.",
							e);
				} catch (TerminalManagementOperationException e1) {
					// ********************** Log **********************
					IgxLog.messageLog.error(this, e1.getMessage());
					// ********************** Log **********************
					
					throw e1;
				}
			}
			
			// (8) 檢查目前的端末清單中是否有結束日期已到需要失效的端末
			synchronizeCheckOutOfDayTerminals();
		}
		
		// ********************** Log **********************
		IgxLog.messageLog.info(this,
				"Synchronizing terminals from database completed.");
		// ********************** Log **********************
		
		// 通知 Terminal Management Listener
		this.terminalManagementListener.afterSynchronizeTerminalNotify();
	}
	
	protected abstract void synchronizeAddTerminals(
			Map<String, Object> terminalFolderIdMap,
			Map<String, BasicTerminalInfoBean> basicTerminalInfoMap)
			throws TerminalManagementOperationException;
	
	protected abstract void synchronizeRemoveTerminals(
			Map<String, Object> terminalFolderIdMap,
			Map<String, BasicTerminalInfoBean> basicTerminalInfoMap)
			throws TerminalManagementOperationException;
	
	private List<BasicTerminalInfoBean> filterInCorrectTerminalIdLen(
					List<BasicTerminalInfoBean> basicTerminalInfoBeanInDbList) {
		List<BasicTerminalInfoBean> filteredList = new LinkedList<BasicTerminalInfoBean>();
		
		for (BasicTerminalInfoBean basicTerminalInfoBeanInDb : basicTerminalInfoBeanInDbList) {
			String terminalId = basicTerminalInfoBeanInDb.getTerminalId();
			if (terminalId.length() != this.terminalIdLen) {
				// ********************** Log **********************
				IgxLog.messageLog.error(
								this,
								"SUDA-324 Synchronizing terminals: the length of terminal id '{}' in database is incorrect.",
								terminalId);
				// ********************** Log **********************
			}
			else {
				filteredList.add(basicTerminalInfoBeanInDb);
			}
		}
		
		return filteredList;
	}
	
	/**
	 * 檢查目前在端末清單中的端末是否有已經失效(超過 End of Effective Date的。
	 */
	private void synchronizeCheckOutOfDayTerminals() {
		// 用來存放已失效的端末
		List<BasicTerminalInfoBean> outOfDayBasicTerminalInfoBeanList = new LinkedList<BasicTerminalInfoBean>();
		
		Set<String> terminalIdSet = this.basicTerminalInfoBeanMap.keySet();
		for (String terminalId : terminalIdSet) {
			BasicTerminalInfoBean basicTerminalInfoBean =
				this.basicTerminalInfoBeanMap.get(terminalId);
			
			if (isAfterEndOfEffectiveDate(basicTerminalInfoBean)) {
				// 將已失效的端末先放入 List 中
				outOfDayBasicTerminalInfoBeanList.add(basicTerminalInfoBean);
			}
		}
		
		// 僅將端末從清單中移除即可(非一般的完整移除動作)
		for (BasicTerminalInfoBean outOfDayBasicTerminalInfoBean : outOfDayBasicTerminalInfoBeanList) {
			this.basicTerminalInfoBeanMap.remove(outOfDayBasicTerminalInfoBean.getTerminalId());
			
			// 通知 TerminalManagerListener
			this.terminalManagementListener.
						terminalOutOfDateNotify(outOfDayBasicTerminalInfoBean);
		}
	}
	
	@Override
	public List<BasicTerminalInfoBean> getEffectiveBasicTerminalInfoBeanList() {
		List<BasicTerminalInfoBean> effectiveBasicTerminaInfoBeanlList = new LinkedList<BasicTerminalInfoBean>();
		
		synchronized(this.basicTerminalInfoBeanMap) {
			Set<String> terminalIdSet = this.basicTerminalInfoBeanMap.keySet();
			for (String terminalId : terminalIdSet) {
				if (isEffectiveTerminalId(terminalId)) {
					effectiveBasicTerminaInfoBeanlList.add(this.basicTerminalInfoBeanMap.get(terminalId));
				}
			}
		}
		
		return effectiveBasicTerminaInfoBeanlList;
	}
	
	@Override
	public boolean isEffectiveTerminalId(String terminalId) {
		BasicTerminalInfoBean basicTerminalInfoBean =
					this.basicTerminalInfoBeanMap.get(terminalId);
		if (null == basicTerminalInfoBean) {
			return false;
		}
		else {
			return
				isEffectiveTerminalId(basicTerminalInfoBean);
		}
	}
	
	@Override
	public boolean isEffectiveTerminalId(
			BasicTerminalInfoBean basicTerminalInfoBean) {
		if (null == basicTerminalInfoBean) {
			return false;
		}
		else {
			synchronized(basicTerminalInfoBean) {
				if (isAfterEndOfEffectiveDate(basicTerminalInfoBean) ||
						isBeforeStartOfEffectiveDate(basicTerminalInfoBean)) {
						return false;
					}
					else {
						return true;
					}
			}
		}
	}
	
	/**
	 * 判斷端末是否已超過失效時間
	 * @param basicTerminalInfoBean
	 * @return
	 */
	protected boolean isAfterEndOfEffectiveDate(BasicTerminalInfoBean basicTerminalInfoBean) {
		Date effectiveEndDate = basicTerminalInfoBean.getEffectiveEndTime();
		
		if (null == effectiveEndDate) {
			return false;
		}
		else {
			if (new Date().after(effectiveEndDate)) {
				return true;
			}
			else {
				return false;
			}
		}
	}
	
	/**
	 * 判斷端末是否還沒到達生效時間
	 * @param basicTerminalInfoBean
	 * @return
	 */
	protected boolean isBeforeStartOfEffectiveDate(BasicTerminalInfoBean basicTerminalInfoBean) {
		return
			new Date().before(basicTerminalInfoBean.getEffectiveStartTime());
	}
	
	/**
	 * 從資料庫中取得所有端末ID的List。
	 * @return
	 */
	protected List<BasicTerminalInfoBean> getTerminalIdListFromDB() {
		// throws DataAccessException
		return 
			this.terminalMgtDao.findAllTerminalsBasicInfo();
	}
	
	protected Map<String, BasicTerminalInfoBean> covertBasicTerminalInfoBeanListToMap(
			List<BasicTerminalInfoBean> basicTerminalInfoBeanList) {
		Map<String, BasicTerminalInfoBean> basicTerminalInfoBeanMap = new HashMap<String, BasicTerminalInfoBean>();

		for (BasicTerminalInfoBean basicTerminalInfoBean : basicTerminalInfoBeanList) {
			basicTerminalInfoBeanMap.put(basicTerminalInfoBean.getTerminalId(),
					basicTerminalInfoBean);
		}

		return basicTerminalInfoBeanMap;
	}
	
	private List<String> getTerminalFolderIdListUnderHaishinOutboxPath() {
		List<String> terminalFolderIdList = new LinkedList<String>();

		// 找出 haishin outbox 底下所有的端未目錄(不包含子目錄)
		File[] terminalFolderArray = new File(this.haishinOutboxPathStr).listFiles();
		
		if (null != terminalFolderArray) {
			// 檢查每個目錄的端末名稱長度是否正確
			synchronized (this.basicTerminalInfoBeanMap) {
				for (File terminalFolder : terminalFolderArray) {
					if (terminalFolder.isDirectory()) {
						String terminalId = terminalFolder.getName();
						if (terminalId.length() == this.terminalIdLen) { // 判斷長度是否正確
							terminalFolderIdList.add(terminalId);
						}
						else {
							// ********************** Log **********************
							if (IgxLog.messageLog.isWarnMode()) {
								IgxLog.messageLog.warn(this,
												"The length of terminal folder name '{}' under haishin outbox is incorrect.",
												terminalId);
							}
							// ********************** Log **********************
						}
					}
				}
			}
		}

		return terminalFolderIdList;
	}
	
	/**
	 * 更新 BasicTerminalInfoBean 內容，包含：
	 * (1) EffectiveStartTime
	 * (2) EffectiveEndTime
	 * @param toBeUpdatedBasicTerminalInfoBean 要更新內容的 BasicTerminalInfoBean
	 * @param referenceBasicTerminalInfoBean 更新參考的 BasicTerminalInfoBean
	 * @return
	 */
	protected void updateBasicTerminalInfoBean (BasicTerminalInfoBean toBeUpdatedBasicTerminalInfoBean,
										   	    BasicTerminalInfoBean referenceBasicTerminalInfoBean) {
		synchronized (toBeUpdatedBasicTerminalInfoBean) {
			toBeUpdatedBasicTerminalInfoBean.setClientType(referenceBasicTerminalInfoBean.getClientType());
			toBeUpdatedBasicTerminalInfoBean.setEffectiveStartTime(referenceBasicTerminalInfoBean.getEffectiveStartTime());
			toBeUpdatedBasicTerminalInfoBean.setEffectiveEndTime(referenceBasicTerminalInfoBean.getEffectiveEndTime());
		}
	}
	
	@Override
	public BasicTerminalInfoBean getBasicTerminalInfoBean(String terminalId) {
		if (this.basicTerminalInfoBeanMap.containsKey(terminalId)) {
			return this.basicTerminalInfoBeanMap.get(terminalId);
		}
		else {
			return null;
		}
	}
	
	// --- getter and setter ---
	public void setTerminalManagementListener(
			TerminalManagementListener terminalManagementListener) {
		this.terminalManagementListener = terminalManagementListener;
	}
}
